package com.sankuai.erp.component.appinit.compiler;


import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;


/**
 * <pre>
 *     @author yangchong
 *     email  : yangchong211@163.com
 *     time   : 2019/5/11
 *     desc   : 每个模块生成一张字表
 *     revise :
 *     GitHub : https://github.com/yangchong211/YCAppTool
 * </pre>
 */
public abstract class BaseGenerateChildTableProcessor extends AbstractProcessor {

    protected String mClassJavaDoc;
    protected String mModuleCoordinate;
    protected String mModuleDependencies;
    protected String mChildTablePrefix;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mClassJavaDoc = "Generated by " + this.getClass().getSimpleName() + ". Do not edit it!\n";

        mModuleCoordinate = getOption(getAptModuleCoordinateKey());
        if (mModuleCoordinate == null) {
            throw new RuntimeException("请在「build.gradle」中配置「" + getAptModuleCoordinateKey() + "」参数");
        }
        mModuleDependencies = getOption(getAptModuleDependenciesKey());

        initChildTablePrefix();
    }

    private void initChildTablePrefix() {
        mChildTablePrefix = "_" + mModuleCoordinate.replace('.', '_').replace('-', '_').replace(':', '_');
        StringBuilder childTablePrefixSb = new StringBuilder(mChildTablePrefix);
        Matcher mc = Pattern.compile("_").matcher(mChildTablePrefix);
        int i = 0;
        while (mc.find()) {
            int position = mc.end() - (i++);
            childTablePrefixSb.replace(position - 1, position + 1, childTablePrefixSb.substring(position, position + 1).toUpperCase());
        }
        mChildTablePrefix = childTablePrefixSb.toString();
    }

    protected abstract String getAptModuleCoordinateKey();

    protected abstract String getAptModuleDependenciesKey();

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        final Set<String> annotationTypes = new LinkedHashSet<>();
        annotationTypes.add(getAnnotationClass().getCanonicalName());
        return annotationTypes;
    }

    protected abstract Class getAnnotationClass();

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        String annotationSimpleName = getAnnotationClass().getSimpleName();
        Set<? extends Element> childAnnotatedElementSet = roundEnv.getElementsAnnotatedWith(getAnnotationClass());
        if (isElementNotEmpty(childAnnotatedElementSet)) {
            info("===============> " + annotationSimpleName + " 不为空 START <===============");

            MethodSpec.Builder constructorMethod = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC);

            insertStatementBeforeAddItem(constructorMethod);

            for (Element childAnnotatedElement : childAnnotatedElementSet) {
                if (validateChildAnnotatedElement(childAnnotatedElement, annotationSimpleName)) {
                    addItem((TypeElement) childAnnotatedElement, constructorMethod);
                }
            }

            TypeSpec type = TypeSpec.classBuilder(mChildTablePrefix + getChildTableSuffix())
                    .superclass(getChildTableSuperClassTypeName())
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(constructorMethod.build())
                    .addJavadoc(mClassJavaDoc)
                    .build();

            generateClass(getGeneratedPackageName(), type);
            info("===============> " + annotationSimpleName + " 不为空 END <===============");
        } else {
            info("===============> " + annotationSimpleName + " 为空 <===============");
        }
        return true;
    }

    protected abstract void insertStatementBeforeAddItem(MethodSpec.Builder constructorMethod);

    protected abstract boolean validateChildAnnotatedElement(Element childAnnotatedElement, String annotationSimpleName);

    protected abstract void addItem(TypeElement element, MethodSpec.Builder constructorMethod);

    protected abstract String getChildTableSuffix();

    protected abstract TypeName getChildTableSuperClassTypeName();

    protected abstract String getGeneratedPackageName();

    protected boolean validateClassImplements(Element element, String interfaceCanonicalName, String annotationSimpleName) {
        if (validateClassAssignable(element, interfaceCanonicalName)) {
            return true;
        } else {
            error(element, annotationSimpleName + " 注解的类必须实现「" + interfaceCanonicalName + "」接口");
            return false;
        }
    }

    protected boolean validateClassAssignable(Element element, String canonicalName) {
        return element.getKind().isClass() && processingEnv.getTypeUtils().isAssignable(element.asType(),
                processingEnv.getElementUtils().getTypeElement(canonicalName).asType());
    }

    protected boolean validateClassSubtype(Element element, String type) {
        return processingEnv.getTypeUtils().isSubtype(element.asType(),
                processingEnv.getElementUtils().getTypeElement(type).asType());
    }

    protected boolean validateClassSubtype(TypeMirror typeMirror, String type) {
        return processingEnv.getTypeUtils().isSubtype(typeMirror,
                processingEnv.getElementUtils().getTypeElement(type).asType());
    }

    protected boolean validateAbstractClass(Element element) {
        Set<Modifier> modifiers = element.getModifiers();
        if (modifiers != null && modifiers.contains(Modifier.ABSTRACT)) {
            return true;
        }
        return false;
    }

    protected void generateClass(String packageName, TypeSpec typeSpec) {
        try {
            JavaFile.builder(packageName, typeSpec).build().writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected TypeName getTypeName(String canonicalName) {
        return ClassName.get(processingEnv.getElementUtils().getTypeElement(canonicalName));
    }

    protected String getOption(String key) {
        return processingEnv.getOptions().get(key);
    }

    protected void info(CharSequence info) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, getLogInfo(info));
    }

    protected void error(Element element, CharSequence info) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, getLogInfo(info), element);
    }

    private String getLogInfo(CharSequence info) {
        return mModuleCoordinate + info;
    }

    protected static boolean isElementNotEmpty(Set<? extends Element> elements) {
        return elements != null && !elements.isEmpty();
    }
}